package ocbn.remotelab.util.date;

import java.util.Vector;
import net.rim.device.api.i18n.SimpleDateFormat;

/**
 * Parses a date format represented either as a String or a SimpleDateFormat
 * object and extracts the date fields and date field separators. A "date field"
 * refers to a type of data found in a date; for example, year, month and
 * minutes are all examples of date fields. A "date field separator" refers to
 * the substring that separates two date fields or a date field and the
 * beginning/end of a string; for example, in the date format "(yy/MM)", the
 * "(", "/", and ")" are considered to be separators.
 * <p>
 * <strong>Note</strong>: This date format parser does not support date formats
 * with two date fields connected, such as "yyMM", or escaped characters in date
 * formats, such as "yy'dMM". 
 *
 * @author Ke Li
 */
public class DateFormatParser {

    /**
     * The reserved characters that are used to represent date fields in the
     * date format string.
     */
    private static final String RESERVED_CHARACTERS = "yMdhHmsSEaz";
    /**
     * A Vector of Strings representing date fields.
     */
    private Vector fields;
    /**
     * A Vector of Strings representing date field separators.
     */
    private Vector fieldSeparators;
    /**
     * The date format to be parsed by this class.
     */
    private SimpleDateFormat dateFormat;
    /**
     * A boolean indicating whether the current date format has been parsed.
     */
    private boolean parsed;

    /**
     * Constructs a new instance from the string representation of the date
     * format.
     *
     * @param dateFormatString    A String representing the date format
     */
    public DateFormatParser(String dateFormatString) {
        this(new SimpleDateFormat(dateFormatString));
    }

    /**
     * Constructs a new instance from a SimpleDateFormat object representing
     * a date format.
     *
     * @param dateFormat    A SimpleDateFormat object representing the date
     *                      format
     */
    public DateFormatParser(SimpleDateFormat dateFormat) {
        this.dateFormat = dateFormat;
        fields = new Vector();
        fieldSeparators = new Vector();
        parsed = false;
    }

    /**
     * Checks if a character is a reserved character representing a date field.
     *
     * @param character     The character to check
     * @return              A boolean indicating whether a character is a
     *                      reserved character
     */
    private static boolean isCharacterReserved(char character) {
        return RESERVED_CHARACTERS.indexOf(character) >= 0;
    }

    /**
     * Parses the date format specified in the constructor and extracts the
     * string representation of date fields and the separators between them.
     */
    private void parseDateFormat() {
        if (!parsed) {

            // Indicates whether the previous character is a separator
            boolean isReadingSeparator = true;

            // String representation of the date format
            String format = dateFormat.toPattern();

            // The starting position of the current separator
            int start = 0;

            // The length of the previous separator
            int previousSeparatorLength = 0;

            // Indicates if the current character is reserved
            boolean isCharacterReserved;

            // Iterate through all characters of the date format string
            for (int i = 0; i < format.length(); i++) {
                isCharacterReserved = isCharacterReserved(format.charAt(i));

                if (isCharacterReserved && isReadingSeparator) {
                    // If the end of the separator is reached,

                    isReadingSeparator = false;
                    fieldSeparators.addElement(format.substring(start, i));
                    previousSeparatorLength = i - start;
                } else if (!isCharacterReserved && !isReadingSeparator) {
                    // If the end of the field is reached,

                    fields.addElement(format.substring(start
                            + previousSeparatorLength, i));
                    start = i;
                    isReadingSeparator = true;
                }
            }
            // Add the last separator or field to the appropriate Vector
            if (isReadingSeparator) {
                // If the last character is part of a separator,

                fieldSeparators.addElement(format.substring(start));
            } else {
                // If the last character is part of a field,

                fields.addElement(format.substring(start
                        + previousSeparatorLength));
                fieldSeparators.addElement("");
            }
            // Set parsed to true, so that this format is not parsed again if
            // this method is called again later
            parsed = true;
        }
    }

    /**
     * Retrieves all the date fields in the date format specified in the
     * constructor as strings.
     *
     * @return      A Vector of Strings containing all the date fields in the
     *              date format specified in the constructor
     */
    public Vector getFieldsAsStr() {
        parseDateFormat();
        return fields;
    }

    /**
     * Retrieves all the date fields in the date format specified in the
     * constructor as integers. The integers correspond to the values of the 
     * constants in SimpleDateFormat or Calendar class that represent date
     * fields. For example, if a field is year, the integer value would
     * be that of SimpleDateFormat.YEAR_FIELD or Calendar.YEAR. 
     *
     * @return      An array of integers containing all the date fields in the
     *              date format specified in the constructor
     */
    public int[] getFieldsAsInt() {
        return dateFormat.getFields();
    }

    /**
     * Retrieves all the date field separators in the date format specified 
     * in the constructor as strings. For example, if "(yy/MM)" is the date
     * format, the "(", "/", and ")" are the separators that will be returned.
     *
     * @return      A Vector of Strings containing all the date field separators
     *              in the date format specified in the constructor
     */
    public Vector getFieldSeparators() {
        parseDateFormat();
        return fieldSeparators;
    }
}
